package org.bendev.ildaplayer;

import java.util.Calendar;
import java.util.LinkedList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.LinearLayout;

public class StatusView extends SurfaceView implements PlayerListener, SurfaceHolder.Callback, GestureHandler.Callback {

	static final String 
		TAG = "StatusView",
		DATE_FORMAT = "yyyy-MM-dd kk:mm:ss",
		TICK_FORMAT = "kk:mm";
	static final long
		SECOND = android.text.format.DateUtils.SECOND_IN_MILLIS,
		MINUTE = android.text.format.DateUtils.MINUTE_IN_MILLIS,
		HOUR = android.text.format.DateUtils.HOUR_IN_MILLIS,
		GO_FULLSCREEN_TIMEOUT = SECOND*2;
	static final int
		BACKGROUND = Color.BLACK,
		MINOR_TICK_COLOR = Color.DKGRAY,
		MAJOR_TICK_COLOR = Color.DKGRAY,
		TICK_LABEL_COLOR = Color.GRAY,
		CURTIME_COLOR = Color.rgb( 128, 0, 0 ),
		INITIAL_SCALE = (int) SECOND,
		MAX_SCALE = (int) SECOND*30,
		MIN_SCALE = (int) (SECOND/2);
	
	
	static class Metrics {
		public final int tickLabelWidth;
		public final Paint paint = new Paint( Paint.ANTI_ALIAS_FLAG );
		public int pixelPerMinorTick, pixelPerMajorTick, minorPerMajor, surfaceWidth, surfaceHeight;
		public long scale, minorTickPeriod, majorTickPeriod;
		
		public Metrics(){
			paint.setTextSize( 24 );
			tickLabelWidth = (int) paint.measureText( DateFormat.format( TICK_FORMAT, 0 ).toString() );			
			setScale( INITIAL_SCALE );
		}
		
		public void setSurfaceSize( int width, int height ){
			this.surfaceWidth = width;
			this.surfaceHeight = height;
		}
		
		public void setScale( long scale ){
			this.scale = scale;
			if( scale/SECOND<3 ){
				minorTickPeriod = MINUTE;
				minorPerMajor = 5;
			}
			else if( scale/SECOND<6 ){
				minorTickPeriod = 5 * MINUTE;
				minorPerMajor = 2;
			}
			else if( scale/SECOND<15 ){
				minorTickPeriod = 10 * MINUTE;
				minorPerMajor = 3;
			}
			else{
				minorTickPeriod = 30 * MINUTE;
				minorPerMajor = 2;
			}
			this.pixelPerMinorTick = (int)( minorTickPeriod/scale );
			this.majorTickPeriod = this.minorTickPeriod * minorPerMajor;
			this.pixelPerMajorTick = (int)( majorTickPeriod/scale );
		}
	}

	static class Ticks extends LinkedList<Calendar>	{
		private static final long serialVersionUID = -8013787403613624152L;
		
		final Metrics metrics;
		
		public Ticks( Metrics metrics ){
			this.metrics = metrics;
		}
		
		public void setPos( Calendar pos, Bitmap buffer ){
			pos = DateUtils.local( pos );
			long posTime = pos.getTimeInMillis();
			posTime-= posTime % metrics.majorTickPeriod;
			posTime-= metrics.majorTickPeriod * ( ( metrics.surfaceWidth / 2 + metrics.pixelPerMajorTick - 1 ) / metrics.pixelPerMajorTick );
			pos.setTimeInMillis( posTime );

			Canvas c = null;
			int offset = 0,
				nTicks = ( ( metrics.surfaceWidth + metrics.tickLabelWidth*2 + metrics.pixelPerMajorTick - 1 ) / metrics.pixelPerMajorTick + 1 ) * metrics.minorPerMajor,
				di = 1;

			if( !isEmpty() ){
				offset = (int)( ( pos.getTimeInMillis() - getFirst().getTimeInMillis() ) / metrics.majorTickPeriod ) * metrics.minorPerMajor;
				if( offset==0 )
					return;
				if( Math.abs( offset )>=nTicks )
					clear();
				else{
					Paint p = new Paint();
					p.setStyle( Paint.Style.FILL );
					p.setColor( BACKGROUND );
					c = new Canvas( buffer );
					int bh = buffer.getHeight(),
						dx = offset * metrics.pixelPerMinorTick;
					if( offset>0 ){
						int w = ( nTicks - offset )*metrics.pixelPerMinorTick + metrics.tickLabelWidth*2;
						c.drawBitmap( Bitmap.createBitmap( buffer, dx, 0, w, bh ), 0, 0, null );
						c.drawRect( w, 0, w+dx, bh, p );
						removeRange( 0, offset );
						offset = size();
					}
					else{
						int w = ( nTicks + offset )*metrics.pixelPerMinorTick;
						c.drawBitmap( Bitmap.createBitmap( buffer, 0, 0, w, bh ), -dx, 0, null );
						c.drawRect( 0, 0, -dx, bh, p );
						removeRange( offset+size(), size() );
						offset = -offset-1;
						nTicks = -1;
						di = -1;
					}
				}
			}
			if( isEmpty() ){
				c = new Canvas( buffer );
				c.drawColor( BACKGROUND );
			}
			
			int textHeight = (int) metrics.paint.getTextSize();

			for( int i=offset; i!=nTicks; i+=di ){
				Calendar tick = DateUtils.add( pos, metrics.minorTickPeriod*i );
				if( di>0 ) addLast( tick );
				else addFirst( tick );
				
				long tickTime = tick.getTimeInMillis();
				int x = metrics.tickLabelWidth + (int)( ( tickTime - posTime ) / metrics.scale );
				int y = 8;
				if( ( tickTime % metrics.majorTickPeriod )==0 ){
					y*= 2;
					String text = DateFormat.format( TICK_FORMAT, tick ).toString();
					int textWidth = (int) metrics.paint.measureText( text );
					metrics.paint.setColor( TICK_LABEL_COLOR );
					c.drawText( text, x - textWidth/2, y + textHeight, metrics.paint );
					metrics.paint.setStrokeWidth( 3 );
					metrics.paint.setColor( MAJOR_TICK_COLOR );
				}
				else{
					metrics.paint.setStrokeWidth( 0 );
					metrics.paint.setColor( MINOR_TICK_COLOR );
				}
				c.drawLine( x, 0, x, y, metrics.paint );
			}
		}
	}
	
	boolean wasPlaying = false, hasDragged = false, hasScaled = false, isFullscreen = false;
	float initialX = 0, initialY = 0;
	long initialScale;
	Object lock = new Object();
	Handler handler = new Handler();
	Runnable goFullscreenCallback = new Runnable(){ public void run(){ setFullscreen(true); } };
	Metrics metrics = new Metrics();
	Ticks ticks;
	Player player;
	Bitmap buffer;
	SurfaceHolder holder;
	Calendar dragStartPos;

	public StatusView(Context context, AttributeSet attrs) {
		super( context, attrs );
	}

	@Override
	public void surfaceChanged( SurfaceHolder holder, int format, int width, int height ) {
		synchronized(lock){
			Log.i(TAG,String.format("surfaceChanged (%d,%d)",width,height));
			metrics.setSurfaceSize( width, height );
			ticks = new Ticks( metrics );
			buffer = Bitmap.createBitmap( metrics.surfaceWidth*2, metrics.surfaceHeight, Bitmap.Config.RGB_565 );
			showStatus();
		}
	}

	@Override
	public void surfaceCreated( SurfaceHolder holder ) {
	}

	@Override
	public void surfaceDestroyed( SurfaceHolder holder ) {
		ticks = null;
		buffer = null;
	}
	
	@Override
	protected void onAttachedToWindow()
	{
		super.onAttachedToWindow();
		holder = getHolder();
		holder.addCallback( this );
	}
	
	@Override
	protected void onDetachedFromWindow()
	{
		super.onDetachedFromWindow();
		holder.removeCallback( this );
		holder = null;
	}

	public void setPlayer( Player player ){
		this.player = player;
		player.setListener( this );
	}
	
	void showStatus(){
		if( holder==null || ticks==null )
			return;
		
		Canvas canvas = holder.lockCanvas();
		int sw = metrics.surfaceWidth, 
			sh = metrics.surfaceHeight;

		Calendar pos = player.getPosition();
		if( pos!=null ){
			ticks.setPos( pos, buffer );

			int dx = metrics.tickLabelWidth+(int)((pos.getTimeInMillis()-ticks.getFirst().getTimeInMillis())/metrics.scale) - sw/2;
			Rect 
				src = new Rect( 0, 0, sw, sh ), 
				dst = new Rect( src );
			src.offset( dx, 0 );
			canvas.drawBitmap( buffer, src, dst, null );
		}
		metrics.paint.setStrokeWidth( 3 );
		metrics.paint.setColor( CURTIME_COLOR );
		canvas.drawLine( sw/2, 2, sw/2, sh-2, metrics.paint );

		if( player==null || player.getState()==Player.State.PAUSED || player.getState()==Player.State.BUFFERING ){
			Drawable d = getResources().getDrawable( player.getState()==Player.State.BUFFERING ? android.R.drawable.ic_lock_idle_charging : android.R.drawable.ic_media_pause );
			d.setBounds( 0, 0, sh, sh );
			d.draw( canvas );
		}
		//canvas.drawText( String.format("scale:%.1f",metrics.scale/(float)SECOND), 0, metrics.surfaceHeight, metrics.paint );
		holder.unlockCanvasAndPost( canvas );
	}
	
	@Override
	public void onDown( float x, float y ) {
		dragStartPos = (Calendar) player.getPosition().clone();
		if( wasPlaying = (player.getState()==Player.State.PLAYING) )
			player.pause();
		initialX = x;
		initialY = y;
		hasDragged = hasScaled = false;
	}

	@Override
	public void onUp( float x, float y ) {
		if( !wasPlaying && !hasDragged && !hasScaled )
			player.start();
	}

	@Override
	public void onMove( float x, float y ) {
		if( hasScaled )
			return;
		float dx = initialX - x;
		if( !hasDragged && Math.abs(dx)<5 )
			return;
		player.seekTo( DateUtils.add( dragStartPos, (int)( dx * metrics.scale ) ) );
		showStatus();
		hasDragged = true;
	}
	
	@Override
	public boolean onScale( ScaleGestureDetector detector ) {
		long scale = Math.max( MIN_SCALE, Math.min( MAX_SCALE, (long)( initialScale * detector.getPreviousSpanX() / detector.getCurrentSpanX() ) ) );
		if( scale!=metrics.scale ) synchronized(lock){
			metrics.setScale( scale );
			ticks.clear();
			showStatus();
		}
		return false;
	}
	
	@Override
	public boolean onScaleBegin( ScaleGestureDetector detector ) {
		initialScale = metrics.scale;
		hasScaled = true;
		return true;
	}
	
	@Override
	public void onScaleEnd( ScaleGestureDetector detector ) {
	}

	@Override
	public void onStateChange( Player.State state ) {
		setFullscreen( state==Player.State.PLAYING );
		showStatus();
	}

	@Override
	public void onPositionChange( Calendar position ) {
		showStatus();
	}
	
	void setFullscreen( boolean fullscreen ){
		if( !fullscreen )
			handler.removeCallbacks( goFullscreenCallback );

		if( isFullscreen == fullscreen )
			return;
		
		isFullscreen = fullscreen;
		View parentView = (LinearLayout) getParent();
		if( fullscreen ){
			parentView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_HIDE_NAVIGATION );
			parentView.setOnSystemUiVisibilityChangeListener( new View.OnSystemUiVisibilityChangeListener() {
				@Override
				public void onSystemUiVisibilityChange( int visibility ) {
					if( visibility==0 ){
						isFullscreen = false;
						handler.removeCallbacks( goFullscreenCallback );
						handler.postDelayed( goFullscreenCallback, GO_FULLSCREEN_TIMEOUT );
					}
				}
			} );
		}
		else{
			parentView.setOnSystemUiVisibilityChangeListener( null );
			parentView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_FULLSCREEN );
		}
	}
}
